package com.linqingying.cangjie.debugger

import com.linqingying.cangjie.debugger.settings.CjDebuggerSettings
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.removeUserData
import com.jetbrains.cidr.ArchitectureType
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
import com.jetbrains.cidr.execution.debugger.backend.gdb.GDBDriver
import com.jetbrains.cidr.execution.debugger.backend.gdb.GDBDriverConfiguration
import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration
import java.io.File


interface CjDebuggerDriverConfigurationProvider {

    fun getDebuggerDriverConfiguration(
        project: Project,
        isElevated: Boolean,
        emulateTerminal: Boolean
    ): DebuggerDriverConfiguration?

    companion object {
        @JvmField
        val EP_NAME: ExtensionPointName<CjDebuggerDriverConfigurationProvider> =
            ExtensionPointName.create("com.linqingying.cangjie.debugger.driverConfigurationProvider")
    }
}


class CjDefaultDebuggerDriverConfigurationProvider : CjDebuggerDriverConfigurationProvider {
    @Suppress("MoveVariableDeclarationIntoWhen")
    override fun getDebuggerDriverConfiguration(
        project: Project,
        isElevated: Boolean,
        emulateTerminal: Boolean
    ): DebuggerDriverConfiguration? {
        val debuggerKind = CjDebuggerSettings.getInstance().debuggerKind
        when (debuggerKind) {
            DebuggerKind.LLDB -> {
                val lldbAvailability = CjDebuggerToolchainService.getInstance().lldbAvailability()
                return when (lldbAvailability) {
                    DebuggerAvailability.Bundled -> CjLLDBDriverConfiguration(isElevated, emulateTerminal)
                    is DebuggerAvailability.Binaries -> CjCustomBinariesLLDBDriverConfiguration(
                        lldbAvailability.binaries,
                        isElevated,
                        emulateTerminal
                    )

                    else -> null
                }
            }

            DebuggerKind.GDB -> {
                val gdbAvailability = CjDebuggerToolchainService.getInstance().gdbAvailability()
                return when (gdbAvailability) {
                    DebuggerAvailability.Bundled -> CjGDBDriverConfiguration(isElevated, emulateTerminal)
                    is DebuggerAvailability.Binaries -> CjCustomBinariesGDBDriverConfiguration(
                        gdbAvailability.binaries,
                        isElevated,
                        emulateTerminal
                    )

                    else -> null
                }
            }
        }
    }
}

open class CjGDBDriverConfiguration(
    private val isElevated: Boolean,
    private val emulateTerminal: Boolean
) : GDBDriverConfiguration() {
    override fun getDriverName(): String = "CangJie GDB"

    // TODO: investigate attach to process feature separately
    override fun isAttachSupported(): Boolean = false
    override fun isElevated(): Boolean = isElevated
    override fun emulateTerminal(): Boolean = emulateTerminal
}

private class CjCustomBinariesGDBDriverConfiguration(
    private val binaries: GDBBinaries,
    isElevated: Boolean,
    emulateTerminal: Boolean
) : CjGDBDriverConfiguration(isElevated, emulateTerminal) {
    override fun getGDBExecutablePath(): String = binaries.gdbFile.toString()

    override fun createDriverCommandLine(
        driver: DebuggerDriver,
        architectureType: ArchitectureType
    ): GeneralCommandLine {
        val cmd = super.createDriverCommandLine(driver, architectureType)
        // `GDBDriverConfiguration` assumes that there are bundled C++ pretty-printers.
        // It's not true in the case of Native Debugging Support plugin outside CLion,
        // and gdb throws an exception `Error while executing Python code`.
        // As a result, CangJie pretty printers are not loaded as well.
        // As a temporary workaround, just remove paths to C++ pretty printers.
        // See https://youtrack.jetbrains.com/issue/CPP-34231
        cmd.removeUserData(GDBDriver.PRETTY_PRINTERS_PATH)
        cmd.removeUserData(GDBDriver.ENABLE_STL_PRETTY_PRINTERS)
        return cmd
    }
}

open class CjLLDBDriverConfiguration(
    private val isElevated: Boolean,
    private val emulateTerminal: Boolean
) : LLDBDriverConfiguration() {
    override fun isElevated(): Boolean = isElevated
    override fun emulateTerminal(): Boolean = emulateTerminal
//    override fun useCangJieTypeSystem(): Boolean =
//        SystemInfo.isWindows && Registry.`is`("com.linqingying.cangjie.debugger.lldb.cangjie.msvc", false)
}

private class CjCustomBinariesLLDBDriverConfiguration(
    private val binaries: LLDBBinaries,
    isElevated: Boolean,
    emulateTerminal: Boolean
) : CjLLDBDriverConfiguration(isElevated, emulateTerminal) {
    override fun getDriverName(): String = "CangJie LLDB"
    override fun useSTLRenderers(): Boolean = false
    override fun getLLDBFrameworkFile(architectureType: ArchitectureType): File = binaries.frameworkFile.toFile()
    override fun getLLDBFrontendFile(architectureType: ArchitectureType): File = binaries.frontendFile.toFile()
}
